/**
	Copyright (c) 2009 ATTi 
	All rights reserved.
	Author: Michael Shu
	Date: 12/28/2009
*/

public class SendEmailController {
	public  Case cs{get; set;}
	private Boolean isSelectAll;
	private Boolean isError;
	private Boolean isTextBody;
	private String userName, userEmail, userProfileName, uTimeZoneSidKey;
	private Boolean isAttEmpty, IsAttNotEmpty;
	private String fromName, fromEmail, fromOrgWideId;  
	private Transient Messaging.SingleEmailMessage newEmail;
	private Transient List<Attachment> lAttachment;
	private String caseThreadId, caselink, emailID, replyToAll,forward;
	private Boolean hasContact;
	private String emailStatus;
	private ID OrgId;
	private String retCancelURL, retSendURL;
	private EmailMessage eMessage;
	public  String currentPageId{get; set;}
	public Boolean display {get; set;}
	public String template_id {get; set;}
	public String attachment_id{get;set;}
	public List<attClass> lAtt{get;set;}
	public String fromInfo {get; set;}
	public String toContactName {get; set;}
	public String toContactId{get; set;}
	public String toEmail{get; set;}
	public String additionalTo {get; set;}
	public String cc {get; set;}
	public String bcc {get; set;}
	public String subject { get; set; }
	public String body { get; set; }
	public Transient Blob theBlob{get;set;}
	public String fileName {get; set;}
	public Integer fileSize {get; set;}
	public String fileContentType {get; set;}
	public List<SelectOption> fromlist{get;set;}
	private String debugCT;

	//parse parameter get the related objects to this case
	private Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 

    public class attClass{
    	private Id attachId;
    	private Id docId;
    	public String fileName {get; set;}
    	public Integer fileSize {get; set;}	
    	public Boolean istoDel {get; set;}
    	public Boolean istoSel {get; set;}
    	
    	public attClass(){
    		istoDel = false;
    		istoSel = true;
    	}
    }
    	
	class toClass{
		String toId;
		String toEmail;
	}
	
    public SendEmailController() {
    	this.currentPageId = ApexPages.currentPage().getParameters().get('id'); // Get current Case Id
    	this.emailID = ApexPages.currentPage().getParameters().get('email_id'); // Get Email Id  
    	this.replyToAll =  ApexPages.currentPage().getParameters().get('replyToAll'); // Get Reply to All info 
    	this.forward = ApexPages.currentPage().getParameters().get('forward'); // Get forward info  	
    }  
    
    private void setInitValue(){
    	this.lAtt = new List<attClass>();
		this.fromName = '';
    	this.fromEmail = '';
		this.additionalTo = ''; 
		this.toEmail = '';
		this.bcc = '';
    	this.cc = '';
    	this.toContactName = '';
    	this.toContactId = '';	
    	this.subject = '';
    	this.body = '';
    	this.attachment_id = '';   
    	this.display = true;
    	this.isError = false;
    	this.isTextBody = true;
    	this.isSelectAll = true;	
    	this.hasContact = false;
    	this.isTextBody = true;
    }  
    	
	public PageReference init(){
		setInitValue();
		if(currentPageId == null && emailID == null){
			return new PageReference(SendEmailControllerMessage.CasePageURL);
		}
		String email_Prefix = gd.get('EmailMessage').getDescribe().getKeyPrefix();
		Boolean isEmailDetailPage = this.currentPageId != null && this.currentPageId.subString(0,3) == email_Prefix;
		if(this.emailID != null || isEmailDetailPage){
			String eId = (this.emailID != null)? this.emailID : this.currentPageId;
			eMessage = SelectContactController.getEmailMessage(eId);
    		if(eMessage == null){
    			System.debug('Case error : Insufficient Privileges');
				return Page.InsufficientPrivilegesPage; 
    		}
    		this.cs = SelectContactController.getCase(eMessage.ParentId);
		}else{ 
			this.cs = SelectContactController.getCase(currentPageId);	
		}	
		if(this.cs == null){
		    System.debug('Case error : Insufficient Privileges');
			return Page.InsufficientPrivilegesPage;  
		}
		setretSendURL(isEmailDetailPage);
		setretCancelURL(isEmailDetailPage);
		setUserinfo();
		setThreadId();
    	setInitFromlist();	
    	if(eMessage != null){
			if(this.forward != null && this.forward == '1'){ 
				forwardEmail(); // foward email
				this.emailStatus = SendEmailControllerMessage.Forwarded;
			}else{
				replyAndToAllEmail(); // reply email
				this.emailStatus = SendEmailControllerMessage.Replied;
			}
    	}else{
    		createNewEmail(); // new email
    		this.emailStatus = SendEmailControllerMessage.Sent;
    	}
    	return null;
	} 
	
	private void setRetSendURL(Boolean isEmailDetailPage){
    	if(this.cs != null){
    		retSendURL = '/' + this.cs.Id;
    	}else{
    		retSendURL = SendEmailControllerMessage.CasePageURL;
    	}
	}
	
	private void setRetCancelURL(Boolean isEmailDetailPage){
    	if(isEmailDetailPage){
    		retCancelURL = SendEmailControllerMessage.EmailDetailPageURL + '?id=' + eMessage.Id;
    	}else{
    		if(this.cs != null){
    			retCancelURL = '/' + this.cs.Id;
    		}else{
    			retCancelURL = SendEmailControllerMessage.CasePageURL;
    		}
    	}
	}
	
	private void setUserinfo(){
		User u = [select ProfileId, TimeZoneSidKey, Profile.Name, Email from User where Id =: UserInfo.getUserId()];
    	String hostStr = String.Valueof(ApexPages.currentPage().getHeaders().get('Host'));
    	String[] ss = hostStr.split('\\.');
    	this.caselink = 'https://' + ss[1] + '.salesforce.com/' + cs.Id ;
    	this.userName = UserInfo.getName();
    	this.userEmail = u.Email;
		this.userProfileName = u.Profile.Name;
		this.uTimeZoneSidKey = u.TimeZoneSidKey;		
	}
	
	public ID getOrgId(){
		if(OrgId == null){
			OrgId = [Select Id from Organization limit 1].Id;
		}
		return OrgId;
	}	
	private void setThreadId(){
		ID organizationId = getOrgId();
		String fromRegExp = '([0-9A-Za-z]{4})([0]{0,14})';
	    String toRegExp   = '$1';
	    String OrgId_trim = String.Valueof(organizationId).substring(0,15).replaceAll(fromRegExp, toRegExp);
    	String CaseId_trim = String.Valueof(cs.Id).substring(0,15).replaceAll(fromRegExp, toRegExp);
    	this.caseThreadId = 'ref:' + OrgId_trim  + '.' + CaseId_trim + ':ref';		
	}	
	
	private void forwardEmail(){
		setSubjectBody('FW');
		if(eMessage.HasAttachment){
	    	List<Attachment> attachments = new List<Attachment>([Select a.Id, a.Name, a.BodyLength  
	    	                                                     from Attachment a 
		                                                         where a.ParentId =: eMessage.Id]);
		    for(Attachment a : attachments){
		    	attClass att = new attClass();
		    	att.fileName = a.Name;
		    	att.attachId = a.Id;
		    	att.fileSize = a.BodyLength;
		    	lAtt.add(att);
		    }			
		}
	}
	
	private void replyAndToAllEmail(){
		setSubjectBody('RE');
		Set<String> all_email_list = new Set<String>();
		for(SelectOption o : fromlist){
			String[] ss = o.getValue().split(':');		
			all_email_list.add(ss[1].toUpperCase());	
		}
		if(eMessage.Incoming){
			this.additionalTo = screenEmails(this.additionalTo, eMessage.FromAddress, all_email_list);
			Boolean isInToAddress = setFromAddressInReply(eMessage.ToAddress);
			if(!isInToAddress){
				 Boolean isInCCAddress = setFromAddressInReply(eMessage.CcAddress);
			}
		}
		if(replyToAll == '1'){
			this.additionalTo = screenEmails(this.additionalTo, eMessage.ToAddress, all_email_list);				
			this.cc = screenEmails(this.cc, eMessage.CcAddress, all_email_list);
			this.bcc = screenEmails(this.bcc, eMessage.BccAddress, all_email_list);
		}
	}
	
	private void setSubjectBody(String title){
		String eSubject = (eMessage.Subject == null)? '' : eMessage.Subject;
		if(eSubject.indexof(title) == 0){
			this.subject = eSubject;
		}else{
			this.subject = title + ': ' + eSubject;
		}
		this.body = '\n\n\n'
		           +'--------------- Original Message ---------------\n'
		           +'From: ' + eMessage.FromName + ' [' + eMessage.FromAddress + ']\n'
		           +'Sent: ' + eMessage.MessageDate + '\n'
		           +'To: ' + eMessage.ToAddress + '\n'
		           +'Subject: ' + eSubject + '\n\n'
		           + ((eMessage.TextBody==null) ? '' : eMessage.TextBody);
	}
		
	private String  screenEmails(String orgAddress, String toAddress, Set<String> all_email_list){
		String transToAddress = orgAddress;
		if(transToAddress != ''){
			transToAddress += ';';
		}
		if(toAddress != null){
			String[] ss = String.Valueof(toAddress).split(';');
			for(String s : ss){
				if(!all_email_list.contains(s.trim().toUpperCase())){
					transToAddress += s + ';';
				}
			}
			if(transToAddress.contains(';')){
				transToAddress = transToAddress.substring(0, transToAddress.lastIndexOf(';')); //trim last comma;
			}
		}
		return transToAddress;
	}	
	
	private Boolean setFromAddressInReply(String toAddress){
		Set<String> allToAddress = new Set<String>();
		Boolean isfound = false;
		if(toAddress != null){
			String[] ss = toAddress.split(';');
			for(String s : ss){
				allToAddress.add(s.trim().toUpperCase());
			}
		}
		for(SelectOption o : fromlist){
			String[] ss = o.getValue().split(':');
			if(allToAddress.contains(ss[1].toUpperCase())){
				fromInfo = o.getValue();
				isfound = true;
				break;
			}
		}
		return isfound;	
	}

	public void createNewEmail(){
		this.subject = (cs.Subject == null)? '' : cs.Subject;
		this.additionalTo = (cs.SuppliedEmail == null)? '' : cs.SuppliedEmail;
		this.toContactName = (cs.Contact.Name == null)? '' : cs.Contact.Name;
		this.toContactId = (cs.ContactId == null)? '' : cs.ContactId;
	}
	
	public void addSelectOptions(String s_name, String s_email, String default_recordtype){
		String email_value, email_label;
		email_value = s_name + ':' + s_email; 
		email_label = '"' + s_name + '" <' + s_email + '>';
		SelectOption opt = new SelectOption(email_value, email_label);
		fromlist.add(opt);
		if(default_recordtype == SendEmailControllerMessage.Any_Record_Type || default_recordtype == cs.RecordType.Name){
			fromInfo = email_value;     // set default from address
		}
	}
	
	public void setInitFromlist() {
		fromlist = new List<SelectOption>();
		addSelectOptions(UserName, UserEmail, null); // add user email as the 1st one
		fromInfo = fromlist[0].getValue();     // set default from address
		
		List<String> email_lists;
		if(userProfileName.startsWith(SendEmailControllerMessage.System_Administrator)){ // System Administrator
			Set<String> all_emails = new Set<String>();
			for(String profile_name : SendEmailControllerMessage.Profile_Email_List.keySet()){
				email_lists = SendEmailControllerMessage.Profile_Email_List.get(profile_name);
				for(String s : email_lists){
					if(!all_emails.contains(s)){
						String[] ss = s.split(';');
						addSelectOptions(ss[0], ss[1], null);	
						all_emails.add(s);	
					}	
				}
			}
		}else{ // non System Administrator
			if(SendEmailControllerMessage.QC_Profile_Email_List_CCOOR.containsKey(userProfileName) && GeneralConstants.RT_CCOOR.contains(cs.RecordType.Name)){
				email_lists = SendEmailControllerMessage.QC_Profile_Email_List_CCOOR.get(userProfileName);
			}else if(SendEmailControllerMessage.QC_Profile_Email_List_NATO.containsKey(userProfileName) && GeneralConstants.RT_NATL.contains(cs.RecordType.Name)){
				email_lists = SendEmailControllerMessage.QC_Profile_Email_List_NATO.get(userProfileName);
			}else{
				email_lists = SendEmailControllerMessage.Profile_Email_List.get(userProfileName);
			}
			if(email_lists != null){
				for(String s : email_lists){
					String[] ss = s.split(';');
					addSelectOptions(ss[0], ss[1], ss[2]);			
				}
			}					
		}
	}
	    
    public String getbodyType(){
    	if(isTextBody){
    		return SendEmailControllerMessage.TextBodyType;
    	}else{
    		return SendEmailControllerMessage.HTMLBodyType;
    	}
    }
    
    public void setbodyType(){
    	isTextBody = !isTextBody;
    }   
    
    public String getSwitchToName(){
    	if(isTextBody){
    		return SendEmailControllerMessage.SwitchToHTML;
    	}else{
    		return SendEmailControllerMessage.SwitchToText;
    	}    	
    }
      
    public Boolean getIsAttEmpty(){
    	return lAtt.isEmpty();
    }

    public Boolean getIsAttNotEmpty(){
    	return !lAtt.isEmpty();
    }
            
    public PageReference addAttchment(){
	    if(attachment_id != ''){
	    	String[] aAttmentId = attachment_id.split(';');
	    	Set<Id> sAttId = new Set<Id>();
	    	for(String aAttId : aAttmentId){
	    		sAttId.add(aAttId);
	    	}
	    	List<Attachment> lexistAttachment = new List<Attachment>([Select a.Id, a.Name, a.BodyLength 
	    	                                                          from Attachment a 
		                                                              where a.Id IN: sAttId and a.IsDeleted =: false]);
		    for(Attachment existAtt : lexistAttachment){
		    	attClass att = new attClass();
		    	att.fileName = existAtt.Name;
		    	att.attachId = existAtt.Id;
		    	att.fileSize = existAtt.BodyLength;
		    	lAtt.add(att);
		    }
	    	attachment_id = '';
	    }else{
	    	if(theBlob != null){
	    		Document newDoc = new Document();
	    		newDoc.Body = this.theBlob;
	    		newDoc.Name = SendEmailControllerMessage.TempDocName;
	    		newDoc.Description = fileName;
	    		newDoc.FolderId = UserInfo.getUserId();
	    		newDoc.ContentType = fileContentType; 
	    		Id documentId;
	    		try{
	    			Database.SaveResult sr_doc = Database.insert(newDoc,false);
	    			documentId = sr_doc.getId();
	    		}catch(Exception ex){
	    			addError(ex.getMessage());
	    			System.debug('Email Fail : Save document Fail ' + ex);
	    			GeneralConstants.mailAnyExceptionToMe('SendEmailController', 'Email Fail : Save document Fail', ex);
	    			return null;
	    		}
	    		attClass att = new attClass();
		    	att.fileName = this.fileName;
		    	att.docId    = documentId;
		    	att.fileSize = this.fileSize;
		    	lAtt.add(att);
		    }
	    }
    	return null;
    }
    
    public PageReference removeAttchment(){
    	if(!lAtt.isEmpty()){
    		Integer i = lAtt.size() - 1;
    		for(; i >= 0 ; i--){
    		   if(lAtt[i].istoDel == true){
    		   	 lAtt.remove(i);
    		   }
    		}
    	}
    	return null;
    }
    
    public PageReference selectAll(){
    	if(!isSelectAll){
    		for(attClass selAtt : lAtt){
    			selAtt.istoSel = true;
    		}
    		isSelectAll = true;
    	}else{
    		for(attClass selAtt : lAtt){
    			selAtt.istoSel = false;
    		}
    		isSelectAll = false;
    	}
    	return null;
    }
              
    public String getTitle(){
    	return SendEmailControllerMessage.Title;
    }  
	
    public PageReference send() {
    	this.isError = false;
    	newEmail = getEmail();
    	if(this.isError){
    		System.debug('Email Fail : isErroe = true');
    		return null;
    	}
    	Messaging.SingleEmailMessage[] lmail = new Messaging.SingleEmailMessage[]{newEmail};
    	try{
    		Messaging.SendEmailResult[] eResult = Messaging.sendEmail(lmail); // send email
    		if(eResult[0].isSuccess()){
    			System.debug('Test : hasContact = ' + hasContact);
    			if(!hasContact){
	    			EmailMessage e = getEmailMessage();
	    			Database.SaveResult sr_message = Database.insert(e,false);
	    			Task newTask = getTask();
    				Database.SaveResult sr_task = Database.insert(newTask,false);
	    			
	    			if(lAttachment != null && !lAttachment.isEmpty()){
	    				if(sr_message.isSuccess()){
	    				    ID messageID = sr_message.getId();
			    			setParentIdforAttachment(messageID);
			    			Database.SaveResult[] sr_att = Database.insert(lAttachment,false);
		    			}else{
		    				System.debug('Message Fail : Create Email Message Fail');
		    			}   		
	    			}
    			}	
    		}
		}catch(System.EmailException ex){
			addError(ex.getMessage());
			System.debug('Email Fail : Send Email Error ' + ex);
    		String ex_str = (ex.getMessage() == null)? '' : ex.getMessage();
     		if(!ex_str.contains(SendEmailControllerMessage.INVALID_EMAIL_ADDRESS) 
    		&& !ex_str.contains(SendEmailControllerMessage.MAXIMUM_SIZE_OF_ATTACHMENT)){
    		    GeneralConstants.mailAnyExceptionToMe('SendEmailController', 'Email Fail : Send Email Error ' + debugCT, ex);
    		}
    		return null;   		
        }catch(Exception ex){
        	addError(ex.getMessage());
    		System.debug('Email Fail : Other Error ' + ex);
    		GeneralConstants.mailAnyExceptionToMe('SendEmailController', 'Email Fail : Other Error', ex);
    		return null;
    	}
    	deleteTempDocument(); //delete temp document.
    	PageReference sendPage;
    	sendPage = new PageReference(this.retSendURL);
    	return sendPage;
    }
    
    public PageReference cancel() {
    	PageReference cancelPage;
    	cancelPage = new PageReference(this.retCancelURL);
		cancelPage.setRedirect(true);
		return cancelPage; 
    }   
    
    private void setParentIdforAttachment(ID messageID){
		if(lAttachment != null && !lAttachment.isEmpty()){
			for(Attachment at : lAttachment){
				at.ParentId = messageID;
			}
		}    	
    }
    
    private EmailMessage getEmailMessage(){
    	EmailMessage em = new EmailMessage();
    	em.FromAddress = fromEmail;
    	em.FromName = fromName;
    	em.ToAddress = toEmail;
    	if(additionalTo != ''){
    		if(toEmail != ''){
    			em.ToAddress += ';' + additionalTo;
    		}else{
    			em.ToAddress = additionalTo;
    		}
    	}
    	em.BccAddress = bcc;
    	em.CcAddress = cc;
		if(isTextBody){
			em.TextBody = body;
		}else{
			//em.HtmlBody = body;
			em.TextBody = body;
		}
		em.ParentId = this.cs.Id;
		em.Subject = subject;
		em.Incoming = false;
		em.Status = this.emailStatus;
		em.MessageDate = Datetime.now();
    	return em;
    }

    private Task getTask(){
    	Task tk = new Task();
    	tk.OwnerId = UserInfo.getUserId();
    	tk.Status = SendEmailControllerMessage.COMPLETED; 
    	tk.Priority = SendEmailControllerMessage.NORMAL;
    	tk.WhatId = this.cs.Id;
    	tk.ActivityDate = Date.today();
    	tk.Subject = subject;
    	tk.Description = body;
    	return tk;
    }   
     
    private Boolean emailFormatCheckerFail(String EmailString){
    	Boolean rst = false;
    	String[] arrayEmailString = EmailString.split(';');
    	String emailPattern = '^[\\w\\.%+-]+@[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,4}$';
    	for(String s : arrayEmailString){
    		String s_trim = s.trim();
    		if(!Pattern.matches(emailPattern, s_trim)){
    			rst = true;
    			break;
    		}
    	}
    	return rst;
    }
    
    private list<String> trim_emails (String s_email){
    	list<String> e = new list<String>();
    	if(s_email != null){
    		String[] e_temp = s_email.split(';');
    		for(String s : e_temp){
    			if(s != null){
    				e.add(s.trim());
    			}
    		}
    	}
    	return e;
    }
    
    private Messaging.SingleEmailMessage getEmail(){
    	Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
		
		//error check
		if(toContactName == '' && additionalTo == ''){
			this.isError = true;
			addError(SendEmailControllerMessage.Error_RequiredToemailMissing);
			System.debug('Email error : Contact Name and additionalTo error');
		}
		if(subject == ''){
			this.isError = true;
			addError(SendEmailControllerMessage.Error_SubjectMissing);
			System.debug('Email error : Subject error');
		}

		if(fromEmail != '' && emailFormatCheckerFail(fromEmail)){
			this.isError = true;
			addError(SendEmailControllerMessage.Error_FromEmailFormatError);
			System.debug('Email error : from email format error');
		}
		if(additionalTo != '' && emailFormatCheckerFail(additionalTo)){
			this.isError = true;
			addError(SendEmailControllerMessage.Error_AdditionalToEmailFormatError);
			System.debug('Email error : additionalTo email format error');
		}
		if(cc != '' && emailFormatCheckerFail(cc)){
			this.isError = true;
			addError(SendEmailControllerMessage.Error_CCEmailFormatError);
			System.debug('Email error : cc email format error');
		}
		if(bcc != '' && emailFormatCheckerFail(bcc)){
			this.isError = true;
			addError(SendEmailControllerMessage.Error_BCCEmailFormatError);
			System.debug('Email error : bcc email format error');
		}		
		// Strings to hold the email addresses to which you are sending the email.
		this.hasContact = false;
		if(toContactName != ''){
			toClass toInfo = findtoContact();
			if(this.isError){
				System.debug('Email error : findtoContact error');
				return null;
			}
			
			toContactId = toInfo.toId;
			toEmail = toInfo.toEmail;
			mail.setTargetObjectId(toContactId);
			if(toEmail == null || toEmail == '' || (toEmail != null && emailFormatCheckerFail(toEmail))){
				this.isError = true;
				addError(SendEmailControllerMessage.Error_InvalidContactEmail);
				System.debug('Email error : To email format error');
			}else{
				this.hasContact = true;
			}
		}		
		if(this.isError){
			return null;
		}
		
		if(bcc != ''){
			String[] bccAddress  = trim_emails(bcc);
			mail.setBccAddresses(bccAddress);
		}
		if(cc != ''){
			String[] ccAddresses = trim_emails(cc);
			mail.setCcAddresses(ccAddresses);
		}		
		if(additionalTo != ''){
			String[] toAddresses = trim_emails(additionalTo);	
			mail.setToAddresses(toAddresses);	
		}
		if(fromInfo != ''){
			String[] f_info = fromInfo.split(':');
			fromName = f_info[0];
			fromEmail = f_info[1];
			OrgWideEmailAddress[] orgWideEmails = [Select Id from OrgWideEmailAddress where Address =: fromEmail]; 
			if(orgWideEmails.size() > 0){
				mail.setOrgWideEmailAddressId(orgWideEmails[0].Id);
			}else{
				mail.setSenderDisplayName(fromName);
				mail.setReplyTo(fromEmail);
			}
		}		
		if(!lAtt.isEmpty()){
			List<Messaging.EmailFileattachment> lattFile = new List<Messaging.EmailFileattachment>();
			lAttachment = new List<Attachment>();
			Set<ID> existAttIds    = new Set<ID>();
			Set<ID> insertedDocIds = new Set<ID>(); 
			for(attClass a : lAtt){
				if(a.istoSel == true && a.istoDel == false){
					if(a.attachId != null){
						existAttIds.add(a.attachId);
					}
					if(a.docId != null){
						insertedDocIds.add(a.docId);	
					}
				}
			}
			debugCT = '';
			if(!existAttIds.isEmpty()){
				List<Attachment> fAttachment = [select Id, Body, Name, ContentType from Attachment where Id IN: existAttIds];
				for(Attachment a : fAttachment){
					Messaging.EmailFileattachment attFile1 = new Messaging.EmailFileattachment();
					attFile1.setBody(a.Body);
					attFile1.setFileName(a.Name);
					if(a.ContentType != null) {attFile1.setContentType(checkContentType(a.ContentType)); debugCT += a.Id + '-' + a.ContentType + ';';}
					lattFile.add(attFile1);	
					Attachment newAtt1 = new Attachment();
					newAtt1.Body = a.Body;
					newAtt1.Name = a.Name;
					newAtt1.ContentType = a.ContentType;
					lAttachment.add(newAtt1);			
				}
			}
			if(!insertedDocIds.isEmpty()){
				List<Document> fDocument = [select Id, Body, Name, Description, ContentType from Document where Id IN: insertedDocIds];
				for(Document d : fDocument){
					Messaging.EmailFileattachment attFile2 = new Messaging.EmailFileattachment();
					attFile2.setBody(d.Body);
					attFile2.setFileName(d.Description);
					if(d.ContentType != null) {attFile2.setContentType(checkContentType(d.ContentType)); debugCT += d.Id + '-' + d.ContentType + ';';}
					lattFile.add(attFile2);
					Attachment newAtt2 = new Attachment();
					newAtt2.Body = d.Body;
					newAtt2.Name = d.Description;
					newAtt2.ContentType = d.ContentType;
					lAttachment.add(newAtt2);		
				}
			}
			mail.setFileAttachments(lattFile);
		}
		// Specify the subject line for your email address.
		String REG_CORE   = 'ref\\s*:\\s*[0-9A-Za-z]{1,15}\\s*\\.\\s*500[0-9A-Za-z]{1,12}\\s*:\\s*ref';
		Pattern refPattern_core   = Pattern.compile(REG_CORE);
		if(subject != null && !refPattern_core.matcher(subject).find()){ // new email
			subject += ' [ ' + caseThreadId + ' ]';
		}
		mail.setSubject(subject);
		// Set to True if you want to BCC yourself on the email.
		mail.setBccSender(false);		
		mail.setUseSignature(false);
		mail.setWhatId(this.cs.Id);
		if(body != null && !refPattern_core.matcher(body).find()){
			body += '\n\n' + caseThreadId;
		}
		body = CaseIOGClass.appendFooterToIOGEmails(this.cs, UserInfo.getUserId(), body); // append footer for IOG sent emails
		if(isTextBody){
			mail.setPlainTextBody(body);
		}else{
			//mail.setHtmlBody(body);
			mail.setPlainTextBody(body);
		}
		return mail;
	}
	
	private String checkContentType(String c){
		Map<String, String> mapCT = new Map<String, String>{
									    'jpg' => 'image/pjpeg'
									  , 'pdf' => 'application/pdf'
									  , 'tif' => 'image/tiff'
									  , 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
									  , 'xls'  => 'application/vnd.ms-excel'
									  , 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
									  , 'msg'  => 'application/octet-stream'
									  , 'txt'  => 'text/plain'};
		String lowerCaseStr = (c == null) ? '' : c.toLowerCase();
		return (mapCT.containsKey(lowerCaseStr)? mapCT.get(lowerCaseStr): c);
	}
	
    private toClass findtoContact(){
    	toClass to = new toClass();
    	to.toId = this.toContactId;
    	to.toEmail = this.toEmail;		
    	Contact[] ct = SelectContactController.getContacts(toContactName);
		if(ct.size() == 1){
			to.toId = ct[0].Id;
			to.toEmail = ct[0].Email;
		}else if(ct.size() > 1){
			if(toContactId != null){
				Contact[] ct_verify = SelectContactController.getContactVerify(toContactId);
				if(ct_verify.size() == 1 && toContactName == ct_verify[0].Name){
					to.toId = ct_verify[0].Id;
					to.toEmail = ct_verify[0].Email;
				}else{
					this.isError = true;
					addError(SendEmailControllerMessage.Error_TooManyContactMatching);
					System.debug('Email error : toContactId is not null, more than one to contact found error');
				}
			}else{
				this.isError = true;
				addError(SendEmailControllerMessage.Error_TooManyContactMatching);
				System.debug('Email error : toContactId is null, more than one to contact found error');
			}
		}else{
			this.isError = true;
			addError(SendEmailControllerMessage.Error_NoMatchingContactFound);
			System.debug('Email error : to contact not found error');
		}
		return to;
    }	
    
    public void refreshBody(){
    	/*
    	Messaging.SingleEmailMessage nEmail;
    	nEmail = new Messaging.SingleEmailMessage();
		nEmail.setWhatId(this.cs.Id);
		nEmail.setTargetObjectId('003R000000H0qhy'); // how to give an null Contact Id
		nEmail.setTemplateId(template_id);
		Messaging.SingleEmailMessage[] lmail_temp = new Messaging.SingleEmailMessage[]{nEmail};
		try{
			Messaging.sendEmail(lmail_temp);
		}catch(Exception ex){
		}
		this.body = nEmail.getPlainTextBody();
		*/
    	List<EmailTemplate> eT = new List<EmailTemplate>([select e.Body, e.HtmlValue, e.TemplateType, e.Subject from EmailTemplate e 
    	                                                  where e.Id =: template_id]);                                                
    	if(eT != null && eT.size() == 1){
    		this.subject = eT[0].Subject;
    		if(eT[0].TemplateType == 'text'){
    			this.body = eT[0].Body;
    			isTextBody = true;
    		}else if(eT[0].TemplateType == 'html'){
    			this.body = eT[0].Body;
    			isTextBody = false;
    		}else{
    			if(isTextBody){
    				this.body = eT[0].Body;
    			}else{
    				this.body = eT[0].Body;
    			}
    		}
    	}
    	String[] aText = new String[2];
    	String[] aId = new String[3];
    	aText[0] = this.body;
    	aText[1] = this.Subject;
    	aId[0] = this.cs.Id;
    	aId[1] = this.toContactId;
    	aId[2] = this.cs.AccountId;
    	aText = renderMergeFields(aId, aText, uTimeZoneSidKey);
    	this.body = aText[0];
    	this.subject = aText[1];
    }
        
    private String[] renderMergeFields(String[] aId, String[] aText, String timeZone){
    	if(aText.isEmpty()){
    		return null;
    	}		
		
		Map<String, String> fieldsValues = new Map<String,String>{}; //fieldname, field value
		Map<String, Id> objects = new Map<String, Id>();
		Map<String,String> keyPrefixMap = new Map<String,String>{};
		Set<String> keyPrefixSet = gd.keySet();
		//special merge fields; Case.Link, Case.Thread_Id
		fieldsValues.put('{!Case.Link}', caselink.substring(0, caselink.length() - 3));// remove last three digi in case id
		fieldsValues.put('{!Case.Thread_Id}', caseThreadId );
				
		for(String sObj : keyPrefixSet){
		   Schema.DescribeSObjectResult r =  gd.get(sObj).getDescribe();
		   String tempName = r.getName();
		   String tempPrefix = r.getKeyPrefix();
		   System.debug('Processing Object['+tempName + '] with Prefix ['+ tempPrefix+']');
		   keyPrefixMap.put(tempPrefix,tempName);
		}
		
		String tPrefix;
		
		for(String sid : aId){
			if(sid != null && sid != ''){
	        	tPrefix = String.Valueof(sid).subString(0,3);
	        	objects.put(keyPrefixMap.get(tPrefix),sid);
	        	System.debug('Case Object Type: ' + keyPrefixMap.get(tPrefix));	
			}		
		}
					
		//special merge field; Sending user
		objects.put('User', null);
		// Organization object
		Organization orgObj = [Select City, Country, Division, Fax, Id, Name, Phone, PostalCode, State, Street
	                           from Organization o limit 1];
	    fieldsValues.put('{!Organization.City}', '');
	    fieldsValues.put('{!Organization.Country}', '');
	    fieldsValues.put('{!Organization.Division}', '');
	    fieldsValues.put('{!Organization.Fax}', '');
	    fieldsValues.put('{!Organization.Id}', '');
	    fieldsValues.put('{!Organization.Name}', '');
	    fieldsValues.put('{!Organization.Phone}', '');
	    fieldsValues.put('{!Organization.PostalCode}', '');
	    fieldsValues.put('{!Organization.State}', '');
	    fieldsValues.put('{!Organization.Street}', '');
	    fieldsValues.put('{!Organization.Address}', '');
	    if (orgObj.City != null){
	    	fieldsValues.put('{!Organization.City}', orgObj.City);
	    }
	    if (orgObj.Country != null){
	    	fieldsValues.put('{!Organization.Country}', orgObj.Country);
	    }
	    if (orgObj.Division != null){
	    	fieldsValues.put('{!Organization.Division}', orgObj.Division);
	    }
	    if (orgObj.Fax != null){
	    	fieldsValues.put('{!Organization.Fax}', orgObj.Fax);
	    }
	    if (orgObj.Id != null){
	    	fieldsValues.put('{!Organization.Id}', orgObj.Id);
	    }
	    if (orgObj.Name != null){
	    	fieldsValues.put('{!Organization.Name}', orgObj.Name);
	    }
	    if (orgObj.Phone != null){
	    	fieldsValues.put('{!Organization.Phone}', orgObj.Phone);
	    }
	    if (orgObj.PostalCode != null){
	    	fieldsValues.put('{!Organization.PostalCode}', orgObj.PostalCode);
	    }
	    if (orgObj.State != null){
	    	fieldsValues.put('{!Organization.State}', orgObj.State);
	    }
	    if (orgObj.Street != null){
	    	fieldsValues.put('{!Organization.Street}', orgObj.Street);
	    }
	    String orgAddress = orgObj.Street + '\n' + orgObj.City + ', ' + orgObj.State + ' ' + orgObj.PostalCode + '\n' + orgObj.Country;
	    fieldsValues.put('{!Organization.Address}', orgAddress);
	    
		for (String objName : objects.keySet()){ 	
			System.debug('objName: ' + objName);  
	    	Schema.SObjectType obj = gd.get(objName) ;
    		Map<String, Schema.SObjectField> M = obj.getDescribe().fields.getMap() ;
	    	String objFields = '';
	    	for (Schema.SObjectField field : M.values()){
    			String fieldname = field.getDescribe().getName();
    			objFields += fieldname + ',';
				if(objName != 'User' && fieldname != 'Id' && fieldname.endsWith('Id') && fieldname != 'ParentId'){
					fieldname = fieldname.substring(0,fieldname.lastIndexOf('Id')) + '.Name'; // eg. ContactId -> Contact.Name 
					objFields += fieldname + ',';
				}
			}
			
			objFields = objFields.substring(0, objFields.lastIndexOf(',')); //trim last comma;
			System.debug('objFields: ' + objFields);  
			Sobject relObj;
			String qryString = 'SELECT ' + objFields + ' FROM ' + objName;
			if (objName !=  'User'){
				qryString = qryString + ' WHERE ' + objName + '.Id =' + '\'' + objects.get(objName) + '\'' ;
				relObj = Database.query(qryString); 	
			} else {
				qryString = qryString + ' WHERE ' + objName + '.Id =' + '\'' + UserInfo.getUserId() + '\'' ; 
		        System.debug('qryString: ' + qryString);
		        relObj = Database.query(qryString) ; 
			}
	        
	        for(String s : M.keySet()) {
	        	 try {
			     	 String fieldname = M.get(s).getDescribe().getName();
			     	 Schema.DisplayType fieldtype = M.get(s).getDescribe().getType();
			     	 System.debug('{!' + objName + '.' + fieldname + '} = ' + relObj.get(s) + ' ; Type = ' + fieldtype);
			         if (relObj.get(s) != null){
			         	if(fieldtype == Schema.DisplayType.DATETIME){
			         		DateTime dt = DateTime.valueof(relObj.get(s));
			         		String convertedDate = dt.format('yyyy-MM-dd HH:mm:ss', timeZone);		
			         		fieldsValues.put('{!' + objName + '.' + fieldname + '}', convertedDate );		     	 
			         	}else{
			         		fieldsValues.put('{!' + objName + '.' + fieldname + '}', String.valueOf(relObj.get(s)) );
			         	}
			         } else {
			         	fieldsValues.put('{!' + objName + '.' + M.get(s).getDescribe().getName() + '}', '');
			         }
			         if(objName != 'User' && fieldname != 'Id' && fieldname.endsWith('Id')){
			         	if(relObj.get(s) != null){
			         		fieldname = fieldname.substring(0,fieldname.lastIndexOf('Id')); // eg. ContactId -> Contact
			         		SObject a = relObj.getSObject(fieldname);
			         		System.debug('{!' + objName + '.' + fieldname + '} = ' + String.valueOf(a.get('Name')));
			         		fieldsValues.put('{!' + objName + '.' + fieldname + '}', String.valueOf(a.get('Name')));
			         	}else{
			         		System.debug('{!' + objName + '.' + fieldname + '} = ' + '');
			         		fieldsValues.put('{!' + objName + '.' + fieldname + '}', '');
			         	}
		         	 }
			     } catch(System.Exception e) {
			         //just ignore. field is not available in query.
			     }
			}
			
			//special merge fields; object owner fields
			if (M.containsKey('ownerid')){
				String ownerId = String.valueOf(relObj.get('ownerid'));
		    	//special merge fields; object owner fields			
		    	Map<Id, User> owners_user = new Map<id, User>([Select Id,  Name, FirstName, LastName, Title, Phone, UserType, IsActive 
		    							    from User 
		    							    Where IsActive = True and Id =: ownerId]);
		
		    	Map<Id, Group> owners_queue = new Map<id, Group>([Select Id,  Name 
		    							    from Group 
		    							    Where Type = 'Queue' and Id =: ownerId]);	
    							    					
				if(owners_user != null && owners_user.get(ownerId) != null){
					if (owners_user.get(ownerId).Name != null){
						fieldsValues.put('{!' + objName + '.' +'OwnerFullName'+ '}', owners_user.get(ownerId).Name);
					} else {
						fieldsValues.put('{!' + objName + '.' +'OwnerFullName'+ '}', '');
					}
					if (owners_user.get(ownerId).FirstName != null){
						fieldsValues.put('{!' + objName + '.' +'OwnerFirstName'+ '}', owners_user.get(ownerId).FirstName);
					} else {
						fieldsValues.put('{!' + objName + '.' +'OwnerFirstName'+ '}', '');
					}
					if (owners_user.get(ownerId).LastName !=  null){
						fieldsValues.put('{!' + objName + '.' +'OwnerLastName'+ '}', owners_user.get(ownerId).LastName);
					} else {
						fieldsValues.put('{!' + objName + '.' +'OwnerLastName'+ '}', '');
					}
					if (owners_user.get(ownerId).Title !=  null){
						fieldsValues.put('{!' + objName + '.' +'OwnerTitle'+ '}', owners_user.get(ownerId).Title);
					} else {
						fieldsValues.put('{!' + objName + '.' +'OwnerTitle'+ '}', '');
					}
					if (owners_user.get(ownerId).Phone !=  null){
						fieldsValues.put('{!' + objName + '.' +'OwnerPhone'+ '}', owners_user.get(ownerId).Phone);
					} else {
						fieldsValues.put('{!' + objName + '.' +'OwnerPhone'+ '}', '');
					}
					
				}else if(owners_queue != null && owners_queue.get(ownerId) != null){
					if (owners_queue.get(ownerId).Name != null){
						fieldsValues.put('{!' + objName + '.' +'OwnerFullName'+ '}', owners_queue.get(ownerId).Name);
					} else {
						fieldsValues.put('{!' + objName + '.' +'OwnerFullName'+ '}', '');
					}
					fieldsValues.put('{!' + objName + '.' +'OwnerFirstName'+ '}', '');	
					fieldsValues.put('{!' + objName + '.' +'OwnerLastName'+ '}', '');
					fieldsValues.put('{!' + objName + '.' +'OwnerTitle'+ '}', '');
					fieldsValues.put('{!' + objName + '.' +'OwnerPhone'+ '}', '');				
				}
			} 	
			
			//need to handle special fields in Case merge templates
			System.debug('here objName: ' + objName);
			    		
	    	if (objName == 'Case'){
		    	try {
		    		Map<Id, CaseSolution> cSols = new Map<Id, CaseSolution>([Select SolutionId, IsDeleted, Id, CaseId From CaseSolution Where IsDeleted = False AND CaseId = :this.cs.Id ]);
		    		List<ID> cSolIds = new List<Id>();
		    		For (Id cSol : cSols.keyset()){
		    			System.debug('cSols.get(cSol).SolutionId: ' + cSols.get(cSol).SolutionId);
		    			cSolIds.add(cSols.get(cSol).SolutionId);
		    		} 	
		    		System.debug('Case Solutions found: ' + cSols.size());	    		
		    		List<Solution> sols = new List<Solution>([Select SolutionNote, SolutionName, Id, (Select Id From Attachments) From Solution Where Id in :cSolIds]);
		    		System.debug('Solutions found: ' + Sols.size());
		    		
		    		if (sols.size() > 0){
			    		String Subject = '';
			    		String Description = '';
			    		String SubjectDescription = '';
			    		Integer count = 1;
			    		for (Solution sol : sols){
			    			Subject += sol.SolutionName + '\r\n\r\n';
			    			Description += sol.SolutionNote + '\r\n\r\n';
			    			SubjectDescription += '----------------------------------------------------------------------------' + '\r\n' +
			    					'(' +count + ') "' + sol.SolutionName + '"' + '\r\n' +
			    					'----------------------------------------------------------------------------' + '\r\n\r\n' +
			    					sol.SolutionNote + '\r\n\r\n'; 
			    			count++ ; 
			    		}
			    			    		
				    	System.debug('Solutions subject: ' + Subject);
				    	fieldsValues.put('{!Case.Solution_Subject}', Subject);
				    	System.debug('Solutions description: ' + Description);
				    	fieldsValues.put('{!Case.Solution_Description}', Description);
				    	fieldsValues.put('{!Case.Solution_Subject_and_Description}', SubjectDescription);
				    	fieldsValues.put('{!Case.Suggested_Solutions}', '');
		    			fieldsValues.put('{!Case.Solution_Attachments}', '');
		    		}
		    		
		    	} catch (Exception e) {
		    		//default to null string, in case there are no attached solutions
			    	fieldsValues.put('{!Case.Solution_Subject}', '');
			    	fieldsValues.put('{!Case.Solution_Description}', '');
			    	fieldsValues.put('{!Case.Solution_Subject_and_Description}', '');
		    		fieldsValues.put('{!Case.Suggested_Solutions}', '');
		    		fieldsValues.put('{!Case.Solution_Attachments}', '');
		    	}
	    	}
		}
    	
		for(Integer i = 0; i < aText.size() ; i++){
			String emailText = aText[i];
			if(emailText == null){
				aText[i] = '';
				continue;
			}
	        for(String s : fieldsValues.keySet()) {
	        	System.debug('merger field Replacing:' + s + ' to ' + fieldsValues.get(s));
	        	emailText = emailText.replace(s, fieldsValues.get(s));
	        }
	        
	        // formula parse {!NullValue(Contact.Name, "Member")} or {!NullValue(Contact.Name, Member)}
	        // 1. {!NullValue(Contact.Name, "Member")} -> {!Contact.Name},Member}
			// 2. if {!Contact.Name} is blank, replace {!Contact.Name},Member} -> Member, otherwise -> {!Contact.Name}
			String fromRegExp = '\\{!NullValue\\(([^,\\r\\n]+),\\s*"([^"\\r\\n]+)"\\)\\}';
	        String toRegExp   = '{!$1},$2}';
	        emailText = emailText.replaceAll(fromRegExp, toRegExp);
	        for(String s : fieldsValues.keySet()) {
	        	System.debug(i + ' : formula matching: s = ' + s + '; value = ' + fieldsValues.get(s));
	        	if(emailText.contains(s)){
	        		System.debug(i + ' : formula Replacing: s = ' + s + ' ; ' + emailText.contains(s) + ';fieldsValues.get(s) = ' + fieldsValues.get(s));
	        		String s_comment = s.replaceAll('(\\{!.*)(\\})', '\\\\$1\\\\$2');
	        		System.debug(' s_comment = ' + s_comment );
	        		fromRegExp = s_comment + ',([^\\}\\r\\n]+)\\}';
	        		if(fieldsValues.get(s) == ''){
	        	    	toRegExp = '$1';
	        	    }else{
	        	    	toRegExp = fieldsValues.get(s);
	        	    }
	        	    emailText = emailText.replaceAll(fromRegExp,toRegExp);	
	        	}
	        }
	        fromRegExp = '\\{![^\\}\\r\\n]+\\},([^\\}\\r\\n]+)\\}';
	        toRegExp = '$1';
	        emailText = emailText.replaceAll(fromRegExp,toRegExp);
	        
	        // ignore the remainings special token
	        fromRegExp = '\\{![^\\}\\r\\n]*\\}'; // We want any number of characters that are not } or newlines between the quotes
	        toRegExp = '';	
	        emailText = emailText.replaceAll(fromRegExp,toRegExp);
	        
	        // change date format from '2009-01-30 18:08:06' to '1/30/2009'
	        fromRegExp = '(\\d{4})-0?(\\d{1,2})-0?(\\d{1,2})\\s\\d{2}:\\d{2}:\\d{2}';
	        toRegExp = '$2/$3/$1'; 
	        emailText = emailText.replaceAll(fromRegExp,toRegExp);
	        aText[i] = emailText;     
		}
    	return aText;
    }
    
    //    Method adds an Error message to the page
    private void addError(String errorMsg){         
        ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR,errorMsg);               
        ApexPages.addMessage(msg);
    } 
        
    //    Method adds an Warning message to the page
    private void addWarning(String warningMsg){         
        ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.WARNING,warningMsg);               
        ApexPages.addMessage(msg);
    }  
    	    
    public PageReference checkSpelling() {
    	return null;
    }
    
    public void deleteTempDocument(){
	    DateTime aFewHrsAgo = DateTime.now().addMinutes(-10); // 10 min ago, clean unused temp doc
	    List<Document> docs = [select Id from Document where Name =: SendEmailControllerMessage.TempDocName 
	                                                     and CreatedDate < :aFewHrsAgo and FolderId =: UserInfo.getUserId()];
	    if(docs != null){
	    	try{
	    		delete docs;
	    	}Catch(Exception ex){
	    		GeneralConstants.mailAnyExceptionToMe('SendEmailController', 'delete dummy doc fail', ex);
	    	}
	    } 
    }
  
}